﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SharePointExpressionBuilder.cs" company="Lambros Vasiliou">
//   Copyright © 2010
// </copyright>
// <summary>
//   Expression builder for regular SharePoint variables.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using Microsoft.SharePoint.Administration;

namespace SPExpressions
{
    using System;
    using System.CodeDom;
    using System.Web;
    using System.Web.Compilation;
    using System.Web.UI;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Utilities;

    /// <summary>
    /// Expression builder for regular SharePoint variables.
    /// </summary>
    [ExpressionPrefix("SharePoint"),
    ExpressionEditor("SPExpressions.SPExpressionBuilderDesigner, SPExpressions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a3c5aa9a0d09d572")]
    public class SharePointExpressionBuilder : ExpressionBuilder
    {

        /// <summary>
        /// Expression to evaluate the current user's login name.
        /// </summary>
        public const string CurrentUserLoginNameExpression = "CURRENTUSERLOGINNAME";

        /// <summary>
        /// Expression to evaluate the current user's email address.
        /// </summary>
        public const string CurrentUserEmailExpression = "CURRENTUSEREMAIL";

        /// <summary>
        /// Expression to evaluate the current user's display name.
        /// </summary>
        public const string CurrentUserDisplayNameExpression = "CURRENTUSERDISPLAYNAME";

        /// <summary>
        /// Expression to evaluate the current user's notes.
        /// </summary>
        public const string CurrentUserNotesExpression = "CURRENTUSERNOTES";

        /// <summary>
        /// Expression to evaluate the current site collection's description.
        /// </summary>
        public const string CurrentSiteDescriptionExpression = "CURRENTSITEDESCRIPTION";

        /// <summary>
        /// Expression to evaluate the current site collection's title.
        /// </summary>
        public const string CurrentSiteTitleExpression = "CURRENTSITETITLE";

        /// <summary>
        /// Expression to evaluate the current site collection's server relative URL.
        /// </summary>
        public const string CurrentSiteUrlExpression = "CURRENTSITEURL";

        /// <summary>
        /// Expression to evaluate the current site collection's logo URL.
        /// </summary>
        public const string CurrentSiteLogoUrlExpression = "CURRENTSITELOGOURL";

        /// <summary>
        /// Expression to evaluate the current site collection's logo description.
        /// </summary>
        public const string CurrentSiteLogoDescriptionExpression = "CURRENTSITELOGODESCRIPTION";

        /// <summary>
        /// Expression to evaluate the current site collection's full URL.
        /// </summary>
        public const string CurrentSiteFullUrlExpression = "CURRENTSITEFULLURL";

        /// <summary>
        /// Expression to evaluate the current site's description.
        /// </summary>
        public const string CurrentWebDescriptionExpression = "CURRENTWEBDESCRIPTION";

        /// <summary>
        /// Expression to evaluate the current site's full URL.
        /// </summary>
        public const string CurrentWebFullUrlExpression = "CURRENTWEBFULLURL";

        /// <summary>
        /// Expression to evaluate the current site's title.
        /// </summary>
        public const string CurrentWebTitleExpression = "CURRENTWEBTITLE";

        /// <summary>
        /// Expression to evaluate the current site's server relative URL.
        /// </summary>
        public const string CurrentWebUrlExpression = "CURRENTWEBURL";

        /// <summary>
        /// Expression to evaluate the current site's logo URL.
        /// </summary>
        public const string CurrentWebLogoUrlExpression = "CURRENTWEBLOGOURL";

        /// <summary>
        /// Expression to evaluate the current site's logo description.
        /// </summary>
        public const string CurrentWebLogoDescriptionExpression = "CURRENTWEBLOGODESCRIPTION";

        /// <summary>
        /// Expression to evaluate a list's title.
        /// </summary>
        public const string ListTitleExpression = "LISTTITLE";

        /// <summary>
        /// Expression to evaluate a list's description.
        /// </summary>
        public const string ListDescriptionExpression = "LISTDESCRIPTION";

        /// <summary>
        /// Expression to evaluate a list's default view URL.
        /// </summary>
        public const string ListDefaultViewTitleExpression = "LISTDEFAULTVIEWTITLE";

        /// <summary>
        /// Expression to evaluate a list's default URL.
        /// </summary>
        public const string ListDefaultViewUrlExpression = "LISTDEFAULTVIEWURL";

        /// <summary>
        /// Expression to evaluate the current list's Title.
        /// </summary>
        public const string CurrentListTitleExpression = "CURRENTLISTTITLE";

        /// <summary>
        /// Expression to evaluate the current list's Description.
        /// </summary>
        public const string CurrentListDescriptionExpression = "CURRENTLISTDESCRIPTION";

        /// <summary>
        /// Expression to evaluate the current list's default view URL.
        /// </summary>
        public const string CurrentListDefaultViewUrlExpression = "CURRENTLISTDEFAULTVIEWURL";

        /// <summary>
        /// Expression to evaluate the current list's default view title.
        /// </summary>
        public const string CurrentListDefaultViewTitleExpression = "CURRENTLISTDEFAULTVIEWTITLE";

        

        /// <summary>
        /// Specifies URL Encoding for the value.
        /// </summary>
        public const string UrlEncoding = "URL";

        /// <summary>
        /// Specifies HTML Encoding for the value.
        /// </summary>
        public const string HtmlEncoding = "HTML";

        /// <summary>
        /// When overridden in a derived class, returns a value indicating whether the current <see cref="T:System.Web.Compilation.ExpressionBuilder"/> object supports no-compile pages. 
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Web.Compilation.ExpressionBuilder"/> supports expression evaluation; otherwise, false.
        /// </returns>
        public override bool SupportsEvaluate
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets the SharePoint value for the specified expression.
        /// </summary>
        /// <param name="expression">The SharePoint variable to look-up.</param>
        /// <param name="designer">Indicates whether the expression is to be evaluated in a designer.</param>
        /// <returns>A <see cref="String"/> containing the value of the given expression.</returns>
        public static string GetSharePointProperty(string expression, bool designer)
        {
            if (string.IsNullOrEmpty(expression))
            {
                throw new NullReferenceException("The expression parameter cannot be null.");
            }

            string value;
            string encoding = string.Empty;
            string param = null;

            var expressionsections = expression.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            if (expressionsections.Length == 1)
            {
                value = expression;
            }
            else
            {
                value = expressionsections[0];


                //encoding always last

                encoding = expressionsections[expressionsections.Length - 1];
                if (expressionsections.Length == 2)
                {
                    if (string.Compare(encoding, HtmlEncoding, StringComparison.OrdinalIgnoreCase) != 0 &&
                        string.Compare(encoding, UrlEncoding, StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        param = encoding;
                        encoding = string.Empty;
                    }
                }
                else
                {
                    param = expressionsections[1];
                }
            }

            // Web Expressions
            if (value.IndexOf("WEB", StringComparison.OrdinalIgnoreCase) > -1)
            {
                var currentWeb = SPContext.Current.Web;
                if (currentWeb == null)
                {
                    throw new Exception("No site in the current context.");
                }

                if (string.Compare(value, CurrentWebUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Server Relative URL." : currentWeb.ServerRelativeUrl;
                }

                if (string.Compare(value, CurrentWebTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Title." : currentWeb.Title;
                }

                if (string.Compare(value, CurrentWebFullUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site URL." : currentWeb.Url;
                }

                if (string.Compare(value, CurrentWebDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Description." : currentWeb.Description;
                }

                if (string.Compare(value, CurrentWebLogoUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Logo URL." : currentWeb.SiteLogoUrl;
                }

                if (string.Compare(value, CurrentWebLogoDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Logo Description." : currentWeb.SiteLogoDescription;
                }
            }
            if (value.IndexOf("SITE", StringComparison.OrdinalIgnoreCase) > -1)
            {
                var currentSite = SPContext.Current.Site;

                if (currentSite == null)
                {
                    throw new Exception("No site collection in the current context.");
                }

                // Site Collection Expressions
                if (string.Compare(value, CurrentSiteFullUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection URL." : currentSite.Url;
                }

                if (string.Compare(value, CurrentSiteUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection Server Relative URL." : currentSite.ServerRelativeUrl;
                }

                if (string.Compare(value, CurrentSiteTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection Title." : currentSite.RootWeb.Title;
                }

                if (string.Compare(value, CurrentSiteDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection Description." : currentSite.RootWeb.Description;
                }

                if (string.Compare(value, CurrentSiteLogoUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection Logo URL." : currentSite.RootWeb.SiteLogoUrl ?? string.Empty;
                }

                if (string.Compare(value, CurrentSiteLogoDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current Site Collection Logo Description." : currentSite.RootWeb.SiteLogoDescription;
                }
            }

            // Current List
            if (value.IndexOf("CURRENTLIST", StringComparison.OrdinalIgnoreCase) > -1)
            {
                var currentList = SPContext.Current.List;
                if (currentList == null)
                {
                    throw new Exception("No list found in the current context.");
                }

                if (string.Compare(value, CurrentListTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current List Title." : currentList.Title;
                }

                if (string.Compare(value, CurrentListDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current List Description." : currentList.Description;
                }

                if (string.Compare(value, CurrentListDefaultViewUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current List Default View URL." : currentList.DefaultViewUrl;
                }

                if (string.Compare(value, CurrentListDefaultViewTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current List Default View Title." : currentList.DefaultView.Title;
                }
            }
            else // List
                if (value.IndexOf("LIST", StringComparison.OrdinalIgnoreCase) > -1)
                {
                    var list = GetList(param);
                    if (list == null)
                    {
                        throw new Exception("No list with the given identity was found.");
                    }

                    if (string.Compare(value, ListTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        value = designer ? "List Title." : list.Title;
                    }

                    if (string.Compare(value, ListDescriptionExpression, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        value = designer ? "List Description." : list.Description;
                    }

                    if (string.Compare(value, ListDefaultViewTitleExpression, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        value = designer ? "List Default View Title." : list.DefaultView.Title;
                    }

                    if (string.Compare(value, ListDefaultViewUrlExpression, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        value = designer ? "List Default View URL." : list.DefaultViewUrl;
                    }
                }

            // Current User.
            if (value.IndexOf("USER", StringComparison.OrdinalIgnoreCase) > -1)
            {
                if (string.Compare(value, CurrentUserDisplayNameExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current User Display Name." : SPContext.Current.Web.CurrentUser.Name;
                }

                if (string.Compare(value, CurrentUserEmailExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current User Email." : SPContext.Current.Web.CurrentUser.Email;
                }

                if (string.Compare(value, CurrentUserLoginNameExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current User Login Name." : SPContext.Current.Web.CurrentUser.LoginName;
                }

                if (string.Compare(value, CurrentUserNotesExpression, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    value = designer ? "Current User Notes." : SPContext.Current.Web.CurrentUser.Notes;
                }
            }

            //encoding
            if (string.Compare(UrlEncoding, encoding, StringComparison.OrdinalIgnoreCase) == 0 && !designer)
            {
                value = HttpUtility.UrlEncode(value);
            }

            else if (string.Compare(HtmlEncoding, encoding, StringComparison.OrdinalIgnoreCase) == 0 && !designer)
            {
                value = HttpUtility.HtmlEncode(value);
            }

            return value;
        }

        /// <summary>
        /// When overridden in a derived class, returns code that is used during page execution to obtain the evaluated expression.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.CodeDom.CodeExpression"/> that is used for property assignment.
        /// </returns>
        /// <param name="entry">The object that represents information about the property bound to by the expression.
        /// </param><param name="parsedData">The object containing parsed data as returned by <see cref="M:System.Web.Compilation.ExpressionBuilder.ParseExpression(System.String,System.Type,System.Web.Compilation.ExpressionBuilderContext)"/>. 
        /// </param><param name="context">Contextual information for the evaluation of the expression.
        /// </param>
        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            return new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(GetType()), "GetSharePointProperty", new CodeExpression[] { new CodePrimitiveExpression(parsedData), new CodePrimitiveExpression(false) });
        }

        /// <summary>
        /// When overridden in a derived class, returns an object that represents an evaluated expression.
        /// </summary>
        /// <returns>
        /// An object that represents the evaluated expression; otherwise, null if the inheritor does not implement <see cref="M:System.Web.Compilation.ExpressionBuilder.EvaluateExpression(System.Object,System.Web.UI.BoundPropertyEntry,System.Object,System.Web.Compilation.ExpressionBuilderContext)"/>.
        /// </returns>
        /// <param name="target">The object containing the expression.
        /// </param><param name="entry">The object that represents information about the property bound to by the expression.
        /// </param><param name="parsedData">The object containing parsed data as returned by <see cref="M:System.Web.Compilation.ExpressionBuilder.ParseExpression(System.String,System.Type,System.Web.Compilation.ExpressionBuilderContext)"/>.
        /// </param><param name="context">Contextual information for the evaluation of the expression.
        /// </param>
        public override object EvaluateExpression(object target, BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            string expression = parsedData.ToString();
            if (string.IsNullOrEmpty(expression))
            {
                return null;
            }

            return GetSharePointProperty(expression, false);
        }

        /// <summary>
        /// When overridden in a derived class, returns an object that represents the parsed expression.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Object"/> containing the parsed representation of the expression; otherwise, null if <see cref="M:System.Web.Compilation.ExpressionBuilder.ParseExpression(System.String,System.Type,System.Web.Compilation.ExpressionBuilderContext)"/> is not implemented.
        /// </returns>
        /// <param name="expression">The value of the declarative expression.
        /// </param><param name="propertyType">The type of the property bound to by the expression.
        /// </param><param name="context">Contextual information for the evaluation of the expression.
        /// </param>
        public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context)
        {
            if (string.IsNullOrEmpty(expression))
            {
                throw new NullReferenceException("An expression needs to be specified.");
            }

            if (propertyType == null)
            {
                throw new NullReferenceException("A property type must be specified.");
            }

            if (!propertyType.Equals(typeof(string)))
            {
                throw new InvalidOperationException("The SharePoint Expression Builder only supports binding to properties of type System.String");
            }

            return expression;
        }

        /// <summary>
        /// Retrieves an <see cref="SPList"/> object based on identity specified.
        /// </summary>
        /// <param name="identity">The identity of the list to return.</param>
        /// <returns>An instance of <see cref="SPList"/> type for the list specified in the <paramref name="identity"/> parameter.</returns>
        private static SPList GetList(string identity)
        {
            try
            {
                if (SPUrlUtility.IsUrlRelative(identity))
                {
                    return SPContext.Current.Web.GetList(identity);
                }

                if (SPUrlUtility.IsUrlFull(identity))
                {
                    using (var site = new SPSite(identity))
                    {
                        using (var web = site.OpenWeb())
                        {
                            var relativeUrl = GetRelativeUrl(site, identity);
                            return web.GetList(relativeUrl);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TraceDiagnostics(TraceSeverity.Unexpected, EventSeverity.Error, ex.Message, new object[] { });
            }

            // assume the given parameter is a list title instead
            return GetListFromTitle(identity);
        }


        /// <summary>
        /// Retrieves an <see cref="SPList"/> object in the current web based on the list title provided.
        /// </summary>
        /// <param name="title">The title of the list to return.</param>
        /// <returns>An <see cref="SPList"/> object of the list in the current web with the title specified in the <paramref name="title"/> parameter.</returns>
        private static SPList GetListFromTitle(string title)
        {
            return SPContext.Current.Web.Lists.TryGetList(title);
        }

        /// <summary>
        /// Converts the given full URL from within the Site Collection to a relative URL.
        /// </summary>
        /// <param name="site">The parent Site Collection</param>
        /// <param name="url">The Site Collection full URL to make relative.</param>
        /// <returns>A string of the relative URL from the given full URL.</returns>
        private static string GetRelativeUrl(SPSite site, string url)
        {
            var uri = new Uri(url);
            var siteUri = new Uri(site.Url);
            return siteUri.MakeRelativeUri(uri).ToString();
        }

        /// <summary>
        /// Writes trace information in the SharePoint ULS logs.
        /// </summary>
        /// <param name="traceSeverity">Indicates the trace severity of the message.</param>
        /// <param name="eventSeverity">Indicates the event severity of the message.</param>
        /// <param name="message">The actual error message.</param>
        /// <param name="parameters">Error message parameters.</param>
        internal static void TraceDiagnostics(TraceSeverity traceSeverity, EventSeverity eventSeverity, string message, object[] parameters)
        {
            var diagnosticsService = SPDiagnosticsService.Local;
            var diagnosticsCategory = new SPDiagnosticsCategory("SharePoint Expressions", traceSeverity, eventSeverity);
            diagnosticsService.WriteTrace(1, diagnosticsCategory, traceSeverity, message, parameters);
        }
    }
}